//! Utility functions and helpers for R-XShell

use crate::error::{Result, ShellError};
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// File system utilities
pub mod fs {
    use super::*;
    use std::fs;
    
    /// Check if a path exists
    pub fn exists<P: AsRef<Path>>(path: P) -> bool {
        path.as_ref().exists()
    }
    
    /// Check if a path is a file
    pub fn is_file<P: AsRef<Path>>(path: P) -> bool {
        path.as_ref().is_file()
    }
    
    /// Check if a path is a directory
    pub fn is_dir<P: AsRef<Path>>(path: P) -> bool {
        path.as_ref().is_dir()
    }
    
    /// Create a directory and all its parent directories
    pub fn mkdir_p<P: AsRef<Path>>(path: P) -> Result<()> {
        fs::create_dir_all(path).map_err(ShellError::Io)
    }
    
    /// Remove a file
    pub fn rm<P: AsRef<Path>>(path: P) -> Result<()> {
        fs::remove_file(path).map_err(ShellError::Io)
    }
    
    /// Remove a directory and all its contents
    pub fn rm_rf<P: AsRef<Path>>(path: P) -> Result<()> {
        fs::remove_dir_all(path).map_err(ShellError::Io)
    }
    
    /// Copy a file
    pub fn cp<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
        fs::copy(from, to).map(|_| ()).map_err(ShellError::Io)
    }
    
    /// Move/rename a file or directory
    pub fn mv<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
        fs::rename(from, to).map_err(ShellError::Io)
    }
    
    /// Read file contents as string
    pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
        fs::read_to_string(path).map_err(ShellError::Io)
    }
    
    /// Write string to file
    pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
        fs::write(path, contents).map_err(ShellError::Io)
    }
    
    /// Get file size in bytes
    pub fn file_size<P: AsRef<Path>>(path: P) -> Result<u64> {
        let metadata = fs::metadata(path).map_err(ShellError::Io)?;
        Ok(metadata.len())
    }
    
    /// Get file modification time
    pub fn modified_time<P: AsRef<Path>>(path: P) -> Result<SystemTime> {
        let metadata = fs::metadata(path).map_err(ShellError::Io)?;
        metadata.modified().map_err(ShellError::Io)
    }
    
    /// Find files matching a pattern
    pub fn find<P: AsRef<Path>>(dir: P, pattern: &str) -> Result<Vec<PathBuf>> {
        let mut results = Vec::new();
        find_recursive(dir.as_ref(), pattern, &mut results)?;
        Ok(results)
    }
    
    fn find_recursive(dir: &Path, pattern: &str, results: &mut Vec<PathBuf>) -> Result<()> {
        if !dir.is_dir() {
            return Ok(());
        }
        
        let entries = fs::read_dir(dir).map_err(ShellError::Io)?;
        
        for entry in entries {
            let entry = entry.map_err(ShellError::Io)?;
            let path = entry.path();
            
            if path.is_dir() {
                find_recursive(&path, pattern, results)?;
            } else if let Some(name) = path.file_name() {
                if name.to_string_lossy().contains(pattern) {
                    results.push(path);
                }
            }
        }
        
        Ok(())
    }
}

/// Path utilities
pub mod path {
    use super::*;
    
    /// Join multiple path components
    pub fn join<I, P>(components: I) -> PathBuf
    where
        I: IntoIterator<Item = P>,
        P: AsRef<Path>,
    {
        components.into_iter().fold(PathBuf::new(), |mut path, component| {
            path.push(component);
            path
        })
    }
    
    /// Get the absolute path
    pub fn absolute<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
        path.as_ref().canonicalize().map_err(ShellError::Io)
    }
    
    /// Get the parent directory
    pub fn parent<P: AsRef<Path>>(path: P) -> Option<PathBuf> {
        path.as_ref().parent().map(|p| p.to_path_buf())
    }
    
    /// Get the file name
    pub fn filename<P: AsRef<Path>>(path: P) -> Option<String> {
        path.as_ref()
            .file_name()
            .map(|name| name.to_string_lossy().to_string())
    }
    
    /// Get the file extension
    pub fn extension<P: AsRef<Path>>(path: P) -> Option<String> {
        path.as_ref()
            .extension()
            .map(|ext| ext.to_string_lossy().to_string())
    }
    
    /// Check if path is relative
    pub fn is_relative<P: AsRef<Path>>(path: P) -> bool {
        path.as_ref().is_relative()
    }
    
    /// Check if path is absolute
    pub fn is_absolute<P: AsRef<Path>>(path: P) -> bool {
        path.as_ref().is_absolute()
    }
}

/// Environment utilities
pub mod env {
    use super::*;
    use std::collections::HashMap;
    
    /// Get an environment variable
    pub fn get<K: AsRef<str>>(key: K) -> Option<String> {
        std::env::var(key.as_ref()).ok()
    }
    
    /// Get an environment variable with a default value
    pub fn get_or<K: AsRef<str>, V: Into<String>>(key: K, default: V) -> String {
        get(key).unwrap_or_else(|| default.into())
    }
    
    /// Set an environment variable
    pub fn set<K: AsRef<str>, V: AsRef<str>>(key: K, value: V) {
        unsafe {
            std::env::set_var(key.as_ref(), value.as_ref());
        }
    }
    
    /// Remove an environment variable
    pub fn remove<K: AsRef<str>>(key: K) {
        unsafe {
            std::env::remove_var(key.as_ref());
        }
    }
    
    /// Get all environment variables
    pub fn all() -> HashMap<String, String> {
        std::env::vars().collect()
    }
    
    /// Get the current working directory
    pub fn current_dir() -> Result<PathBuf> {
        std::env::current_dir().map_err(ShellError::Io)
    }
    
    /// Get the home directory
    pub fn home_dir() -> Option<PathBuf> {
        get("HOME")
            .or_else(|| get("USERPROFILE"))
            .map(PathBuf::from)
    }
    
    /// Get the temporary directory
    pub fn temp_dir() -> PathBuf {
        std::env::temp_dir()
    }
}

/// String utilities
pub mod string {
    /// Escape shell special characters
    pub fn escape_shell(s: &str) -> String {
        if s.chars().any(|c| " \t\n\r|&;<>(){}[]$`\"'*?~".contains(c)) {
            format!("\"{}\"", s.replace('"', "\\\""))
        } else {
            s.to_string()
        }
    }
    
    /// Split a command line into program and arguments
    pub fn split_command_line(cmd: &str) -> Vec<String> {
        let mut parts = Vec::new();
        let mut current = String::new();
        let mut in_quotes = false;
        let mut escape_next = false;
        
        for ch in cmd.chars() {
            if escape_next {
                current.push(ch);
                escape_next = false;
            } else if ch == '\\' {
                escape_next = true;
            } else if ch == '"' {
                in_quotes = !in_quotes;
            } else if ch.is_whitespace() && !in_quotes {
                if !current.is_empty() {
                    parts.push(current.clone());
                    current.clear();
                }
            } else {
                current.push(ch);
            }
        }
        
        if !current.is_empty() {
            parts.push(current);
        }
        
        parts
    }
    
    /// Join command parts into a command line
    pub fn join_command_line(parts: &[String]) -> String {
        parts.iter().map(|s| escape_shell(s)).collect::<Vec<_>>().join(" ")
    }
}

/// Time utilities
pub mod time {
    use super::*;
    
    /// Get current timestamp in seconds since Unix epoch
    pub fn now() -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs()
    }
    
    /// Get current timestamp in milliseconds since Unix epoch
    pub fn now_millis() -> u128 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis()
    }
    
    /// Format duration as human-readable string
    pub fn format_duration(duration: Duration) -> String {
        let secs = duration.as_secs();
        let millis = duration.subsec_millis();
        
        if secs >= 60 {
            let mins = secs / 60;
            let secs = secs % 60;
            format!("{}m {}s", mins, secs)
        } else if secs > 0 {
            format!("{}.{:03}s", secs, millis)
        } else {
            format!("{}ms", millis)
        }
    }
    
    /// Parse duration from string (e.g., "30s", "5m", "1h")
    pub fn parse_duration(s: &str) -> Result<Duration> {
        let s = s.trim();
        if s.is_empty() {
            return Err(ShellError::parse("Empty duration string"));
        }
        
        let (num_str, unit) = if let Some(pos) = s.find(|c: char| c.is_alphabetic()) {
            s.split_at(pos)
        } else {
            (s, "s") // Default to seconds
        };
        
        let num: f64 = num_str.parse()
            .map_err(|_| ShellError::parse(format!("Invalid number: {}", num_str)))?;
        
        let multiplier = match unit {
            "ms" | "millis" | "milliseconds" => 0.001,
            "s" | "sec" | "secs" | "seconds" => 1.0,
            "m" | "min" | "mins" | "minutes" => 60.0,
            "h" | "hr" | "hrs" | "hours" => 3600.0,
            "d" | "day" | "days" => 86400.0,
            _ => return Err(ShellError::parse(format!("Unknown time unit: {}", unit))),
        };
        
        let total_secs = num * multiplier;
        Ok(Duration::from_secs_f64(total_secs))
    }
}

/// Process utilities
pub mod process {
    
    /// Get current process ID
    pub fn current_pid() -> u32 {
        std::process::id()
    }
    
    /// Check if a process is running (Unix-like systems only)
    #[cfg(unix)]
    pub fn is_process_running(pid: u32) -> bool {
        use std::fs;
        fs::metadata(format!("/proc/{}", pid)).is_ok()
    }
    
    /// Check if a process is running (Windows)
    #[cfg(windows)]
    pub fn is_process_running(pid: u32) -> bool {
        use std::process::Command;
        Command::new("tasklist")
            .args(&["/FI", &format!("PID eq {}", pid)])
            .output()
            .map(|output| {
                String::from_utf8_lossy(&output.stdout)
                    .contains(&pid.to_string())
            })
            .unwrap_or(false)
    }
    
    /// Get the parent process ID (Unix-like systems only)
    #[cfg(unix)]
    pub fn parent_pid() -> Option<u32> {
        std::env::var("PPID")
            .ok()
            .and_then(|s| s.parse().ok())
    }
    
    /// Get the parent process ID (Windows - not implemented)
    #[cfg(windows)]
    pub fn parent_pid() -> Option<u32> {
        None // Not easily available on Windows
    }
}